#coding: utf8

from Jugador import Jugador
from Carta import Carta

class JugadorInteligente(Jugador):
	'''Clase que representa a un jugador inteligente de corazones'''
	def __init__ (self,id_jugador,nombre):
		"""Crea el jugador desde su id y su nombre. El id es un numero en el rango 0..3, el nombre es una cadena,
		la mano es una lista de 4 listas (una por palo) y cartas levantadas es una lista con 4 listas (una por cada
		jugador) donde el índice está dado por el id del jugador correspondiente y cada lista contiene las cartas que 
		levanta cada jugador desde el comienzo hasta el fin de la mano."""
		self.id_jugador = id_jugador
		self.nombre = nombre
		self.mano = [[],[],[],[]]
		self.cartas_levantadas = [[],[],[],[]]
		
	#~ corazones = self.mano[0]
	#~ diamantes = self.mano[1]
	#~ picas = self.mano[2]
	#~ treboles = self.mano[3]
	
	def es_primero(self):
		return Carta(2,Carta.TREBOLES) in self.mano[Carta.TREBOLES]
	
	def obtener_nombre_jugador(self):	#Utilizado en pruebas
		'''Devuelve una cadena con el nombre del jugador'''
		return self.nombre
		
	def obtener_mano(self): #Utilizado en pruebas
		'''Devuelve la mano del jugador'''
		return self.mano
	
	def obtener_cantidad_cartas_en_mano(self):
		'''Cuenta la cantidad de cartas en la mano del jugador y la devuelve'''
		cantidad=0
		for palo in self.mano:
			for carta in palo:
				cantidad+=1
		return cantidad
		
	def obtener_cartas_levantadas(self):
		'''Devuelve el atributo cartas_levantadas'''
		return self.cartas_levantadas
		
	def recibir_carta(self,carta):
		'''Recibe una carta y la agrega a su mano separándolas por palo
		y de manera ordenada.'''
		if carta.obtener_palo() == Carta.CORAZONES:
			self.mano[0].append(carta)
		elif carta.obtener_palo() == Carta.DIAMANTES:
			self.mano[1].append(carta)
		elif carta.obtener_palo() == Carta.PICAS:
			self.mano[2].append(carta)
		elif carta.obtener_palo() == Carta.TREBOLES:
			self.mano[3].append(carta)
		self.ordenar_mano(self.mano)
		
	def ordenar_cartas(self,cartas):
		'''Recibe una lista de cartas y las ordena según su número sin tener en cuenta el palo'''
		ordenar_cartas_por_insercion(cartas)
		
	def ordenar_mano(self,mano):
		'''Ordena la mano completa del jugador'''
		for palo in (self.mano[0],self.mano[1],self.mano[2],self.mano[3]):
			self.ordenar_cartas(palo)
	
	def recibir_cartas_pasadas(self, id_jugador, cartas):
		'''Antes de comenzar la mano y despues de haber devuelto sus cartas, recibe
		3 cartas del oponente con id id_jugador y las agrega a su mano junto con las
		de su correspondiente palo'''
		for carta in cartas:
			self.recibir_carta(carta)
			
	def jugar_la_mas_alta(self,cartas):
		'''Devuelve la carta con número mas alto de "cartas"
		Pre: las cartas están ordenadas de menor a mayor'''
		return cartas.pop()	
	
	def jugar_la_mas_baja(self,cartas):
		'''Devuelve la carta de número mas bajo de "cartas"
		Pre: las cartas están ordenadas de menor a mayor'''
		return cartas.pop(0)
		
	def jugar_carta_especifica(self,carta):
		'''Devuelve y elimina la carta de la mano.
		Pre: La carta debe estar en la mano del jugador'''
		palo = carta.obtener_palo()
		for i_carta in range (len(self.mano[palo])):
			if carta == (self.mano[palo])[i_carta]:
				return (self.mano[palo]).pop(i_carta)
		
	def jugar_baja_inmediata(self,cartas_jugadas):
		'''El jugador juega la carta inmediatamente más baja que la 
		más alta (del mismo palo) de las cartas pasadas por parámetro.
		Si el jugador no tiene una más baja, lanza ValueError'''
		# "candidata" es la carta más alta del palo en juego 
		candidata = self.reconocer_maxima_carta(cartas_jugadas)
		
		numero = candidata.obtener_numero()
		# busca la carta dentro de su mano, en el palo en juego
		palo_a_buscar = self.mano[candidata.obtener_palo()]
		if numero==1:
			#Si la candidata es el AS la menor inmediata es la maxima carta de las de ese palo
			return self.mano[candidata.obtener_palo()].pop()
		for i_carta in (range(len(palo_a_buscar)))[::-1]:
			#Recorre la lista al reves, y, sabiendo que esta ordenada de menor a mayor, la primera que sea menor sera la menor inmediata
			if palo_a_buscar[i_carta].obtener_numero()<numero and not palo_a_buscar[i_carta].obtener_numero()==1:
				return	self.mano[candidata.obtener_palo()].pop(i_carta)
		raise ValueError("En su mano no hay carta más baja que la más alta de la mesa")
		
	def reconocer_maxima_carta(self,cartas_jugadas):
		'''Recibe una lista de cartas y devuelve la carta más alta en número
		del palo de la carta ubicada en el índice 0 de "cartas_jugadas"'''
		if cartas_jugadas[0].obtener_numero()==1:
			return cartas_jugadas[0]
		palo = cartas_jugadas[0].obtener_palo()
		numero_max = cartas_jugadas[0].obtener_numero()
		carta_max = cartas_jugadas[0]
		for i_carta in xrange(1,len(cartas_jugadas)):
			carta = cartas_jugadas[i_carta]
			numero_nuevo = carta.obtener_numero()
			if carta.obtener_palo() == palo:
				#Si la carta es el as del palo en juego la devuelve
				if numero_nuevo==1:
					return carta
				if numero_nuevo > numero_max:
					numero_max = numero_nuevo
					carta_max = carta
		return 	carta_max

	def un_jugador_pretende_la_luna(self,nro_jugada):
		'''Metodo a ser utilizado a partir de la 9na jugada para saber si alguien esta utilizando la estrategia de juntar
		todos los corazones y la q de picas. Devuelve True en caso de que alguien este usando la estrategia o False en caso contrario'''
		if nro_jugada<9:
			raise ValueError ("Todavia es prematuro decir si alguien pretende la luna o no")
		for jugador in xrange (len(self.cartas_levantadas)):
			if len(self.cartas_levantadas[jugador])>=nro_jugada: 
			#El jugador supone que si la cantidad de cartas especiales juntadas por un jugador es mayor al numero de jugadas esta buscando el cielo
				return True
		return False
		
	def jugar_palo_con_menos_cartas(self,excepto_palos = []):
		'''"excepto_palos" es una lista de palos en los cuales no va a buscar
		cartas para jugar,El jugador intenta jugar dandole prioridad a deshacerse
		de el palo del cual menos cartas tenga. Ejemplo: si tiene 2 cartas de
		corazones, 5 de picas, 3 de trebol y 3 de diamantes, primero
		intentará arrojar los corazones. Devuelve la más alta de éste palo.
		el parámetro por defecto es una lista vacía.'''
		cantidad = 13 #Maxima cantidad posible de un mismo palo
		cartas_por_palo = []
		for i in range(0,4):
			if not i in excepto_palos and len(self.mano[i]) != 0 and len(self.mano[i]) < cantidad:
				cantidad = len(self.mano[i])
				cartas_por_palo = self.mano[i]
		return self.jugar_la_mas_alta(cartas_por_palo)
		
	def tiene_palo(self,palo):
		'''Devuelve True si el jugador tiene cartas del palo o
		False si no tiene cartas de ese palo'''
		if len(self.mano[palo])!=0:
			return True
		return False
		
	def hay_cartas_especiales(self,cartas):
		'''Devuelve True si entre las "cartas" hay alguna carta especial o False
		si no hay ninguna carta especial.Recibe por parametro una lista de instancias 
		de "Carta"'''
		for carta in cartas:
			if carta.obtener_palo() == Carta.CORAZONES or carta == Carta(12,Carta.PICAS):
				return True
		return False
		
	def devolver_cartas_a_pasar(self,id_jugador):	
		'''Antes de comenzar la mano, retira de su mano y devuelve las 3 cartas
		 del palo que menos cartas tenga para pasarle al oponente con id id_jugador'''	
		cartas_a_pasar = []
		for n in xrange(3):
			cartas_a_pasar.append(self.jugar_palo_con_menos_cartas())
		return cartas_a_pasar
		
	def conocer_jugada(self, cartas_jugadas, id_primer_jugador, id_jugador_que_levanto):
		"""Luego de terminada la jugada, se informa al jugador sobre las cartas que
		se jugaron, en que orden y cual fue el id del jugador que levanto las cartas
		del juego."""
		for carta in cartas_jugadas:
			if carta==Carta(12,Carta.PICAS) or carta.obtener_palo()==Carta.CORAZONES:
				self.cartas_levantadas[id_jugador_que_levanto].append(carta)
		
	def jugar_carta(self,nro_mano, nro_jugada, cartas_jugadas, corazon_jugado):
		'''Saca una carta de su mano y la devuelve jugandola. Recibe el numero de
		mano, el numero de jugada, las cartas ya jugadas en la mesa en esa jugada
		y un booleano que indica si ya se jugaron corazones en las jugadas previas.
		Si ya hay cartas_jugadas y el jugador posee una carta del palo de la primer
		carta debe jugarla obligatoriamente.
		Si nro_jugada es 1 y no hay cartas_jugadas, el jugador debe jugar el 2 de
		treboles.
		Si nro_jugada es 1 el jugador no puede jugar ni un corazon ni la Q de picas.
		Si no hay cartas_jugadas y corazon_jugado es False el jugador no podra jugar
		un corazon salvo que no tenga otra carta.'''
		#Si va primero en la primer jugada, tira el 2 de tréboles
		if nro_jugada == 1 and self.es_primero():
			return self.jugar_carta_especifica(Carta(2,Carta.TREBOLES))
		if not len(cartas_jugadas)==0:
			#Reconocimiento del palo en juego
			palo_en_juego=cartas_jugadas[0].obtener_palo()
		# Si es la primer jugada pero no va primero
		if nro_jugada == 1 and not self.es_primero():
			if self.tiene_palo(palo_en_juego):
				#sabiendo que no puede haber cartas que sumen trata de deshacerse de la más alta
				return self.jugar_la_mas_alta(self.mano[palo_en_juego])
			else:
				#Si no tiene cartas del palo en juego pero tiene cartas de algún otro palo qe no sea corazones
				if self.tiene_palo(Carta.DIAMANTES) or self.tiene_palo(Carta.PICAS) or self.tiene_palo(Carta.TREBOLES):
					carta_a_jugar = self.jugar_palo_con_menos_cartas([palo_en_juego,Carta.CORAZONES])
				#Si no tiene cartas del juego y además, solo tiene corazones, puede tirar corazones
				else:
					carta_a_jugar = self.jugar_palo_con_menos_cartas([palo_en_juego])
				#si juega la q de picas, se intenta jugar la carta de picas inmediatamente más baja a la Q
				if carta_a_jugar == Carta(12,Carta.PICAS):
					#La vuelve a incorporar a su mano
					self.recibir_carta(carta_a_jugar)
					try:
						return self.jugar_baja_inmediata([Carta(12,Carta.PICAS)])
					#Si la única carta de picas que tiene es la Q, juega el único palo restante
					except ValueError:						
						excepto_palos=[palo_en_juego,Carta.CORAZONES,Carta.PICAS]
						return self.jugar_palo_con_menos_cartas(excepto_palos)
				return carta_a_jugar
		elif nro_jugada!=1:
			if nro_jugada==12: #En la jugada 12 ya no verifica si alguien esta jugando a alcanzar la luna y se reinicia el atributo cartas_levantadas
				self.cartas_levantadas = [[],[],[],[]]
			if not len(cartas_jugadas)==0:
				if self.tiene_palo(palo_en_juego):
					#A partir de la mano 9 si se sospecha que alguien esta intentando buscar el cielo trata de arruinarle la estrategia tirando las mas alta
					if 9<=nro_jugada<12 and self.un_jugador_pretende_la_luna(nro_jugada):
						return self.jugar_la_mas_alta(self.mano[palo_en_juego])
					if self.hay_cartas_especiales(cartas_jugadas):
						#intenta jugar la baja inmediata a la más alta de la mesa para no llevarse corazones ni q de picas
						try:
							return self.jugar_baja_inmediata(cartas_jugadas)
						#si no tiene carta más baja, tira la más alta de ese palo
						except ValueError:
							return self.jugar_la_mas_alta(self.mano[palo_en_juego])
					else:
						return self.jugar_la_mas_alta(self.mano[palo_en_juego])
				#si no tiene cartas del palo en juego:
				else:
					#Si tiene q de picas trata de deshacerse de ella
					if Carta(12,Carta.PICAS) in self.mano[Carta.PICAS]:
						return self.jugar_carta_especifica(Carta(12,Carta.PICAS))
					#Si no tiene la q de picas, pero sí tiene corazones, se deshace del corazón mas alto
					if self.tiene_palo(Carta.CORAZONES):
						return self.jugar_la_mas_alta(self.mano[Carta.CORAZONES])
					#En caso de que no tenga la q de picas ni corazones sigue con la estrategia de deshacerse de un palo	
					return self.jugar_palo_con_menos_cartas()
			else:
				#Si es primero y todavía no se jugaron corazones
				if not corazon_jugado:
					# juega el palo con menos cartas excepto corazones (si tiene otro)
					if self.tiene_palo(Carta.DIAMANTES) or self.tiene_palo(Carta.PICAS) or self.tiene_palo(Carta.TREBOLES):
						return self.jugar_palo_con_menos_cartas([Carta.CORAZONES])
					#Si no tiene otra cosa que corazones:
					else:
						return self.jugar_la_mas_baja(self.mano[Carta.CORAZONES])
				#Si ya se jugaron corazones, puede jugar también corazones
				else:
					return self.jugar_palo_con_menos_cartas()

#Auxiliar: funciones de ordenamiento por inserción
					
def ordenar_cartas_por_insercion(lista):
	'''Ordena una lista de instancias de Carta según el método de inserción'''
	for i in xrange(len(lista)-1):
		if lista[i+1].obtener_numero() < lista[i].obtener_numero() or lista[i].obtener_numero()==1:
			reubicar(lista,i+1)
	#por último, ponemos los ases al final (como máximo puede haber 4)
	for i in xrange(len(lista)):
		#como máximo puede haber 4
		for x in range(4):
			if lista[i].obtener_numero() == 1:
				A = lista.pop(i)
				lista.append(A)
			
def reubicar(lista,pos):
	'''Reubica a la carta que está en la posición "pos" de la lista. "Pos" debe ser una posición válida'''
	carta = lista[pos]
	p_aux = pos
	while p_aux > 0:
		if carta.obtener_numero() > lista[p_aux-1].obtener_numero():
			break
		lista[p_aux] = lista[p_aux-1]
		p_aux -= 1
	lista[p_aux] = carta

				
